package com.ant.backstage.config.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * shiro 核心api
 *
 * Subject：用户主体
 *
 * SecurityManager：安全管理器
 *
 * Realm：Shiro 连接数据
 *
 * 参考 ：https://blog.csdn.net/qq_36707668/article/details/122010008
 *
 **/
@Configuration
public class ShiroConfig {

    /**
     * shiro的全局缓存管理器
     * @return
     */
    //ShiroFilter过滤所有请求
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
            @Qualifier("securityManager") SecurityManager securityManager, ShiroFilterChainDefinition chainDefinition) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 必须的设置。我们自定义的Realm此时已经被设置到securityManager中了
        factoryBean.setSecurityManager(securityManager);
        //这里的/login是后台的接口名,非页面，如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//        factoryBean.setLoginUrl("/");
        //这里的/index是后台的接口名,非页面,登录成功后要跳转的链接
//        factoryBean.setSuccessUrl("/index");
        // 注册我们写的过滤器
        Map<String, Filter> filters = factoryBean.getFilters();
        filters.put("jwtAuth", new JwtAuthenticatingFilter());
        //限制同一帐号同时在线的个数
//        filtersMap.put("kickout", kickoutSessionControlFilter());
        //配置自定义登出 覆盖 logout 之前默认的LogoutFilter
        filters.put("logout", shiroLogoutFilter());
        factoryBean.setFilters(filters);

        // 设置请求的过滤规则。其中过滤规则中用到了我们注册的过滤器：jwtAuth 过滤链
        factoryBean.setFilterChainDefinitionMap(chainDefinition.getFilterChainMap());
        return factoryBean;
    }

    @Bean("jwtRealm")
    public Realm jwtRealm() {
        JwtRealm jwtRealm = new JwtRealm();
        jwtRealm.setCacheManager(cacheManager());
        jwtRealm.setCachingEnabled(true);
        jwtRealm.setAuthenticationCachingEnabled(true); // 开启认证缓存
        jwtRealm.setAuthorizationCachingEnabled(true); // 开启授权缓存
        jwtRealm.setAuthenticationCacheName("authenticationCache");//设置缓存名称--认证
        jwtRealm.setAuthorizationCacheName("authorizationCache");//设置缓存名称--授权
        return jwtRealm;
    }


    @Bean(name="securityManager")
    public DefaultWebSecurityManager shiroSecurityManager(@Qualifier("jwtRealm") Realm jwtRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        securityManager.setRealms(Collections.singletonList(jwtRealm));
        securityManager.setRealm(jwtRealm);
        return securityManager;
    }
    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix("");
        return redisCacheManager;
    }
    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("localhost");
        redisManager.setPort(6379);
        redisManager.setExpire(60*60*24);// 配置缓存过期时间
        redisManager.setDatabase(0);
        // redisManager.setPassword(password);
        return redisManager;
    }
    /**
     * 禁用session, 不保存用户登录状态。保证每次请求都重新认证。
     * 需要注意的是，如果用户代码里调用Subject.getSession()还是可以用session，
     * 如果要完全禁用，要配合上过滤规则的noSessionCreation的Filter来实现

    @Bean
    protected SessionStorageEvaluator sessionStorageEvaluator(){
        DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }
     */

    //session管理
    @Bean
    public SessionManager sessionManager() {
        ShiroSessionManager sessionManager = new ShiroSessionManager();
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        // 配置监听 自定义
        listeners.add(sessionListener());
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        sessionManager.setSessionDAO(redisSessionDAO());
        // 全局会话超时时间（单位毫秒），默认30分钟 暂时设置为10秒钟 用来测试
//        sessionManager.setGlobalSessionTimeout(1800000);//单位毫秒
        // 是否开启删除无效的session对象 默认为true
        sessionManager.setDeleteInvalidSessions(true);
        // 是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(false);
        // 设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        // 设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler
        // 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        sessionManager.setSessionValidationInterval(1*1000*60);//单位毫秒
        // 取消url 后面的 JSESSIONID，设置为false为取消
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        //暂时设置为 5秒 用来测试
        sessionManager.setSessionValidationInterval(10*1000);
        return sessionManager;
    }
    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
      RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setKeyPrefix("USER_SHIRO_SESSION:");
        return redisSessionDAO;
    }

    /**
     * 配置session监听
     */
    @Bean
    public SessionListener sessionListener() {
        ShiroSessionListener sessionListener = new ShiroSessionListener();
        return sessionListener;
    }

    //设置cookie
    @Bean
    public Cookie sessionIdCookie(){
        //这个参数是cookie的名称
        Cookie sessionIdCookie=new SimpleCookie("STID");
        //setcookie的httponly属性如果设为true的话，会增加对xss防护的安全系数。它有以下特点：

        //setcookie()的第七个参数
        //设为true后，只能通过http访问，javascript无法访问
        //防止xss读取cookie
        sessionIdCookie.setHttpOnly(true);
        sessionIdCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        sessionIdCookie.setMaxAge(-1);
        return sessionIdCookie;
    }
    /**
     * 设置请求的过滤规则
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/**.js", "noSessionCreation,anon");
        chainDefinition.addPathDefinition("/**.ccs", "noSessionCreation,anon");
        chainDefinition.addPathDefinition("/**.ico", "noSessionCreation,anon");
        //接口文档不拦截
        chainDefinition.addPathDefinition("/**.html", "noSessionCreation,anon"); //静态文件 接口文档不做拦截
        chainDefinition.addPathDefinition("/webjars/**", "anon");
        chainDefinition.addPathDefinition("/swagger/**", "anon");
        chainDefinition.addPathDefinition("/v2/api-docs/**", "anon");
        chainDefinition.addPathDefinition("/swagger-resources/**", "anon");

        //登录注册
        chainDefinition.addPathDefinition("/api/user/login", "anon");//login不做认证，noSessionCreation的作用是用户在操作session时会抛异常,不支持session会话
        chainDefinition.addPathDefinition("/api/user/register", "anon");//注册
        chainDefinition.addPathDefinition("/api/user/selectUserContact", "anon");//获取用户联系方式
        chainDefinition.addPathDefinition("/api/user/selectUserName", "anon");//用户名是否存在
        chainDefinition.addPathDefinition("/api/v1/common/getVerificationCode", "anon");//找回密码验证码
        chainDefinition.addPathDefinition("/api/v1/common/sendMail", "anon");//邮件
        chainDefinition.addPathDefinition("/api/v1/common/checkVerifyCode", "anon");//校验验证码
        chainDefinition.addPathDefinition("/api/user/updatePwd", "anon");//忘记密码
        //秒杀接口 放行为了压测
        chainDefinition.addPathDefinition("/api/v1/activity/spike", "anon");

        // 注意第2个参数的"jwtAuth"需要与上面的 filters.put("jwtAuth", new JwtAuthenticatingFilter()); 一致
        chainDefinition.addPathDefinition("/api/user/logout", "jwtAuth[permissive]"); //做用户认证，permissive参数的作用是当token无效时也允许请求访问，不会返回鉴权未通过的错误
        chainDefinition.addPathDefinition("/**", "jwtAuth"); // 默认进行用户鉴权
        return chainDefinition;
    }
    /**
     * 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     * @return
     */
    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager shiroSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(shiroSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }

    //自定义退出
    public ShiroLogoutFilter shiroLogoutFilter(){
        ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter();
        //配置登出后重定向的地址，等出后配置跳转到登录接口
        shiroLogoutFilter.setRedirectUrl("/user/login");
        return shiroLogoutFilter;
    }

    /**
     * LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部
     * 自动分别调用了Initializable.init()和Destroyable.destroy()方法，从而达到管理shiro bean生命周期的目的。
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

}
